
#include "area.h"
#include "exit.h"
#include "terrainTable.h"
#include "world.h"

Room::Room( const unsigned long& vnum, Area* area ) {
  try {
    char query[MAX_BUFFER];

    sprintf( query, "INSERT IGNORE INTO rooms ( areaID, vnum ) VALUES ( %lu, %lu );", area->ID(), vnum );
    World::Instance().getMysql()->insert( query );
    ID( World::Instance().getMysql()->getInsertID() );

    this->area( area );
    area->rooms().insert( std::make_pair( vnum, this ) );
    this->vnum( vnum );
    flags( 0 );
    name( "Undefined" );
    terrain( area->terrain() );
    for ( register unsigned int u = 0; u < 6; ++u ) {
      _exits[u] = NULL;
    }

  } catch ( MysqlException me ) {
    fprintf( stderr, "Failed to create room for area %lu: %s\n", area->ID(), me.getMessage().c_str() );
    return;
  }

  return;
}

Room::Room( Area* area, ROW& row ) {
  this->area( area );
  ID( row["roomID"] );
  vnum( row["vnum"] );
  name( row["name"] );
  description( row["description"] );
  smell( row["smell"] );
  sound( row["sound"] );
  terrain( TerrainTable::Instance().find( row["terrain"] ) );
  flags( row["flags"] );
  for ( register unsigned int u = 0; u < 6; ++u ) {
    _exits[u] = NULL;
  }
  return;
}

Room::~Room( void ) {
  for ( register unsigned int u = 0; u < 6; ++u ) {
    if ( _exits[u] ) {
      delete _exits[u];
    }
  }
  return;
}

void Room::exit( const unsigned short& direction, Exit* exit ) { // public
  if ( NORTH <= direction && direction <= DOWN ) {
    _exits[direction] = exit;
  }
  return;
}

Exit* Room::exit( const unsigned short& direction ) { // public
  if ( NORTH <= direction && direction <= DOWN ) {
    return _exits[direction];
  }
  return NULL;
}

Exit* Room::exit( const std::string& direction ) { // public
  return exit( Exit::string2dir( direction ) );
}

Object* Room::findObject( const std::string& name ) { // public
  for ( std::list<Object*>::iterator it = objects().begin(); it != objects().end(); ++it ) {
    if ( (*it)->matchesKeyword( name ) ) {
      return *it;
    }
  }
  return NULL;
}

Creature* Room::findCreature( const std::string& name ) { // public
  for ( std::list<Creature*>::iterator it = creatures().begin(); it != creatures().end(); ++it ) {
    if ( (*it)->matchesKeyword( name ) ) {
      return *it;
    }
  }
  return NULL;
}

void Room::send( const char* format, Creature* creature, void* arg1, void* arg2, const unsigned long& target ) { // public
  std::string input( format );
  std::string message;

  // Bail if conditions allow...
  if ( strlen( format ) == 0 || creatures().empty() ) {
    return;
  }

  for ( std::list<Creature*>::iterator c_it = creatures().begin(); c_it != creatures().end(); ++c_it ) {
    // Skip if the target is wrong...
    if  (    ( target == TO_CREATURE  && *c_it != creature )
          || ( target == TO_VICT      && ( *c_it != arg1 || *c_it != arg2 ) )
          || ( target == TO_NOTVICT   && ( *c_it == creature || *c_it == arg1 || *c_it == arg2 ) )
          || ( target == TO_ROOM      && *c_it == creature )
        ) {
      continue;
    }
    // Build our message...
    message.clear();
    for ( std::string::iterator it = input.begin(); it != input.end(); ++it ) {
      if ( *it == '$' ) {
        switch ( *(it+1) ) {
          case 's': message.append( (const char*)arg1 );                              break;
          case 'S': message.append( (const char*)arg2 );                              break;
          case 'p': message.append( (*c_it)->seeName(creature) );                     break;
          case 'c': message.append( (*c_it)->seeName((Creature*)arg1) );              break;
          case 'C': message.append( (*c_it)->seeName((Creature*)arg2) );              break;
          case 'o': message.append( ((Object*)arg1)->shortname() );                   break;
          case 'O': message.append( ((Object*)arg2)->shortname() );                   break;
          case 'e': message.append( Exit::name(((Exit*)arg1)->direction()) );         break;
          case 'E': message.append( Exit::name(((Exit*)arg2)->direction()) );         break;
          case 'n': message.append( Exit::inverseName(((Exit*)arg1)->direction()) );  break;
          case 'N': message.append( Exit::inverseName(((Exit*)arg2)->direction()) );  break;
        }
        ++it;
      } else {
        message.append( 1, *it );
      }
    }
    message[0] = toupper(message[0]);
    (*c_it)->send( message );
  }

  return;
}

void Room::send_cond( const char* format, Creature* creature, void* arg1, void* arg2, const unsigned long& target ) { // public
  std::string input( format );
  std::string message;

  // Bail if conditions allow...
  if ( strlen( format ) == 0 || creatures().empty() ) {
    return;
  }

  for ( std::list<Creature*>::iterator c_it = creatures().begin(); c_it != creatures().end(); ++c_it ) {
    // Skip if the target is wrong...
    if  (    ( target == TO_CREATURE  && *c_it != creature )
          || ( target == TO_VICT      && ( *c_it != arg1 || *c_it != arg2 ) )
          || ( target == TO_NOTVICT   && ( *c_it == creature || *c_it == arg1 || *c_it == arg2 ) )
          || ( target == TO_ROOM      && *c_it == creature )
          || ( (*c_it)->canSee( creature ) == SEE_NOTHING )
        ) {
      continue;
    }
    // Build our message...
    message.clear();
    for ( std::string::iterator it = input.begin(); it != input.end(); ++it ) {
      if ( *it == '$' ) {
        switch ( *(it+1) ) {
          case 's': message.append( (const char*)arg1 );                              break;
          case 'S': message.append( (const char*)arg2 );                              break;
          case 'p': message.append( (*c_it)->seeName(creature) );                     break;
          case 'c': message.append( (*c_it)->seeName((Creature*)arg1) );              break;
          case 'C': message.append( (*c_it)->seeName((Creature*)arg2) );              break;
          case 'o': message.append( ((Object*)arg1)->shortname() );                   break;
          case 'O': message.append( ((Object*)arg2)->shortname() );                   break;
          case 'e': message.append( Exit::name(((Exit*)arg1)->direction()) );         break;
          case 'E': message.append( Exit::name(((Exit*)arg2)->direction()) );         break;
          case 'n': message.append( Exit::inverseName(((Exit*)arg1)->direction()) );  break;
          case 'N': message.append( Exit::inverseName(((Exit*)arg2)->direction()) );  break;
        }
        ++it;
      } else {
        message.append( 1, *it );
      }
    }
    message[0] = toupper(message[0]);
    (*c_it)->send( message );
  }

  return;
}

void Room::save( void ) { // public
  try {
    char query[MAX_BUFFER];

    sprintf( query,
      "UPDATE rooms SET     \
        name = '%s',        \
        description = '%s', \
        smell = '%s',       \
        sound = '%s',       \
        terrain = '%s',     \
        flags = %lu         \
       WHERE roomID = %lu   \
       LIMIT 1;",
      Mysql::addslashes(name()).c_str(),
      Mysql::addslashes(description()).c_str(),
      Mysql::addslashes(smell()).c_str(),
      Mysql::addslashes(sound()).c_str(),
      Mysql::addslashes(terrain()->name()).c_str(),
      flags(),
      ID()
    );
    World::Instance().getMysql()->update( query );

  } catch ( MysqlException me ) {
    fprintf( stderr, "Failed to save room %lu: %s\n", ID(), me.getMessage().c_str() );
    return;
  }

  // Save exits...
  for ( register unsigned short u = 0; u < 6; ++u ) {
    if ( exit( u ) ) {
      exit( u )->save();
    }
  }

  return;
}

bool Room::destroy( void ) { // public
  unsigned long tempID = ID();

  // Get everything out of the room before we annihilate it...
  if ( !clear() ) {
    return false;
  }

  try {
    char query[MAX_BUFFER];

    sprintf( query,
      " DELETE              \
        FROM rooms          \
        WHERE roomID = %lu  \
        LIMIT 1;",
      ID()
    );
    // Get rid of this (from the DB)...
    World::Instance().getMysql()->remove( query );
    // Get rid of this (from parent area)...
    area()->rooms().erase( vnum() );
    // Get rid of outbound exits...
    for ( register unsigned short u = 0; u < 6; ++u ) {
      if ( exit( u ) ) {
        exit( u )->destroy();
        exit( u, NULL );
      }
    }
    // Get rid of inbound exits (this takes a bit more effort)...
    for ( std::set<Area*,area_comp>::iterator a = World::Instance().getAreas().begin(); a != World::Instance().getAreas().end(); ++a ) {
      for ( std::map<unsigned long,Room*>::iterator r = (*a)->rooms().begin(); r != (*a)->rooms().end(); ++r ) {
        for ( register unsigned short u = 0; u < 6; ++u ) {
          if ( r->second->exit( u ) && r->second->exit( u )->target() == this ) {
            r->second->exit( u )->destroy();
            r->second->exit( u, NULL );
          }
        }
      }
    }
    // Get rid of this (from memory)...
    delete this;

  } catch ( MysqlException me ) {
    fprintf( stderr, "Failed to delete room %lu: %s\n", tempID, me.getMessage().c_str() );
    return false;
  }

  // Success!
  return true;
}

bool Room::clear( void ) { // public
  // Clear Objects...
  for ( std::list<Object*>::iterator it = objects().begin(); it != objects().end(); ++it ) {
    delete *it;
  }
  objects().clear();
  // Clear Creatures...
  while ( creatures().size() ) {
    if ( !World::Instance().transport( *(creatures().begin()), 0 ) ) {
      return false;
    }
  }
  creatures().clear();
  return true;
}

std::string Room::listFlags( void ) { // public
  std::string output;
  unsigned long mask = 1;

  for ( unsigned long u = 0; u < sizeof(unsigned long)*8; ++u ) {
    if ( flag( mask ) ) {
      output.append( Room::getFlagName( mask ) ).append( 1, ' ' );
    }
    mask <<= 1;
  }

  return output;
}

const char* Room::getFlagName( const unsigned long& flag ) { // public static
  switch( flag ) {
    case RFLAG_NOMAP:   return "nomap";
    case RFLAG_NOGATE:  return "nogate";
    default:            return "unknown";
  }
}

unsigned long Room::getFlagType( const std::string& flag ) { // public static
  if ( Regex::strPrefix( flag, "nomap" ) ) {
    return RFLAG_NOMAP;
  } else if ( Regex::strPrefix( flag, "nogate" ) ) {
    return RFLAG_NOGATE;
  } else {
    return RFLAG_UNDEFINED;
  }
}

